/**
 *
 *      Copyright 2008 Oliver Eichner <o.eichner@gmail.com>
 *      
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 3 of the License, or
 *      (at your option) any later version.
 *      
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *      
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 * 
 *		Parts included from HDM
 *		hdm.c - Hard Disk Metadata
 *		Copyright (C) Finnbarr P. Murphy 2010  <fpm[AT]fpmurphy.com>
 *		Read more: http://blog.fpmurphy.com/2010/05/hard-disk-metadata.html#ixzz16WBhY72R
 *
 * @file Device.cpp
 * Device Class to store informations from tableau-parm
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
#include "Device.h"

/** 
 * @brief Standart konstruktor for a Device Object
 * @param devName kernelname of device
 */
Device::Device(string devName) {
	id = NULL;
	fd = 0;

    deviceName = devName;
    bridge_dco_in_use = false;
    bridge_dco_support = false;
    bridge_declare_write_blocked = false;
    bridge_declare_write_errors = false;
    bridge_hpa_in_use = false;
    bridge_hpa_support = false;
    bridge_security_in_use = false;
    bridge_security_support = false;
    bridge_writes_permitted = false;
    fetchSys();
}

Device::~Device() {

}

void Device::setComment(string nComment) {
    comment = nComment;
}

string Device::getComment() {
    return comment;
}

string Device::getDeviceName() {
    return deviceName;
}

string Device::getVendor() {
    return vendor;
}

string Device::getModel() {
    return model;
}

string Device::getSerial() {
return serial;
}

int Device::getSizeMB() {
    return sizeMB;
}

string Device::getSizeBLK() {
    stringstream blksize;
    string out;
    blksize << (sizeBlk);
    blksize >> out;
    return out;
}

//Tableau-Bridge getter

string Device::getBridge_chan_index() {
    return bridge_chan_index;
}

string Device::getBridge_chan_type() {
    return bridge_chan_type;
}

bool Device::getBridge_writes_permitted() {
    return bridge_writes_permitted;
}

bool Device::getBridge_declare_write_blocked() {
    return bridge_declare_write_blocked;
}

bool Device::getBridge_declare_write_errors() {
    return bridge_declare_write_errors;
}

string Device::getBridge_serial() {
    return bridge_serial;
}

string Device::getBridge_vendor() {
    return bridge_vendor;
}

string Device::getBridge_model() {
    return bridge_model;
}

string Device::getBridge_firmware_date() {
    return bridge_firmware_date;

}

string Device::getBridge_firmware_time() {
    return bridge_firmware_time;
}
// ## Drive Information ##

string Device::getBridge_drive_vendor() {
    return bridge_drive_vendor;
}

string Device::getBridge_drive_model() {
    return bridge_drive_model;
}

string Device::getBridge_drive_serial() {
    return bridge_drive_serial;
}

string Device::getBridge_drive_revision() {
    return bridge_drive_revision;
}

//## Drive HPA/DCO/Security Information ##

bool Device::getBridge_security_in_use() {
    return bridge_security_in_use;
}

bool Device::getBridge_security_support() {
    return bridge_security_support;
}

bool Device::getBridge_dco_support() {
    return bridge_dco_support;
}

bool Device::getBridge_dco_in_use() {
    return bridge_dco_in_use;
}

bool Device::getBridge_hpa_support() {
    return bridge_hpa_support;
}

bool Device::getBridge_hpa_in_use() {
    return bridge_hpa_in_use;
}

string Device::getBridge_drive_capacity() {
    return bridge_drive_capacity;
}

string Device::getBridge_hpa_capacity() {
    return bridge_hpa_capacity;
}

string Device::getBridge_dco_capacity() {
    return bridge_dco_capacity;
}

string Device::getBridge_dco_challenge_key() {
    return bridge_dco_challenge_key;
}

/**
 * @brief Get the size-string of the device either in MB,GB or TB, dependend on sizeMB..
 */
string Device::getSize() {
	return capacity;
    /*if (sizeMB > 0) {
        stringstream sstr;

        if (sizeMB < 999) {
            sstr << sizeMB;
            sstr >> size;
            size.append(" MB");

        } else if (sizeMB > 999 && sizeMB <= 999999) {
            sstr << (sizeMB / 1000);
            sstr >> size;
            size.append(" GB");
        } else if (sizeMB > 999999) {
            sstr << (sizeMB / 1000000);
            sstr >> size;
            size.append(" TB");
        }
        cout << "newSize: " << size << endl;
        return size;
    } else
        return "error";
        */
}

/**
 * @brief Fetch /sys/block informations
 */
void Device::fetchSys() {



    static struct hd_driveid hd;
    //int fd;
    
    //char *device;

	string fullDeviceName="/dev/";
	fullDeviceName.append(this->deviceName);
    if (geteuid() >  0) {
        printf("ERROR: Must be root to use\n");
        exit(1);
    }

    if ((fd = open(fullDeviceName.data(), O_RDONLY|O_NONBLOCK)) < 0) {
        printf("ERROR: Cannot open device %s\n", fullDeviceName.data());
        exit(1);
    }
    id = (__u16*)get_diskinfo(fd);
    g  = get_geometry(fd);
   if (ioctl(fd, HDIO_GET_IDENTITY, &hd) < 0 ) {
   		cout << "model: "<<hd.model<<endl;
        if (errno == -ENOMSG) {
            fprintf(stderr, "ERROR: No hard disk identification information available\n");
        } else {
            perror("ERROR: HDIO_GET_IDENTITY");
            //exit(1);
        }
        
    }

close(fd);
dump((char*)fullDeviceName.data());



/*    fstream Datei;
    string text, sysFile, tmpSysFile;
    sysFile = "/sys/block/";
    sysFile += deviceName;


    //size abfragen
    tmpSysFile = sysFile + "/size";
    Datei.open(tmpSysFile.data(), ios::in);
    getline(Datei, text);
    cout << "sizeLine:" << text << endl;
    
    sizeBlk = atol(text.data());
    cout << "sizeBlk:" << sizeBlk << endl;
    cout << "stest: "<< (((sizeBlk * 512) / 1000) / 1000) << endl;
    sizeMB = (int) (((sizeBlk * 512) / 1000) / 1000);
    cout << "sizeMB:" << sizeMB << endl;

    Datei.close();
*/


    //sysFile += "/device/";
/*
    //Model name abfragen
    tmpSysFile = sysFile;
    tmpSysFile += "model";
    Datei.open(tmpSysFile.data(), ios::in);
    getline(Datei, text);
    model = text;
    //remoce spaces at the end of the string
    while (model.find_last_of(" ") == model.size() - 1) {

        model.erase(model.find_last_of(" "));
    }
    if (!model.empty()) {
        cout << "model gefunden: " << model << endl;

    } else {
        cerr << "kein model name gefunden" << endl;
        model = "n/a";
    }

    Datei.close();
*/
    //Vendor name abfragen
    /*tmpSysFile.clear();
    tmpSysFile = sysFile;
    tmpSysFile += "vendor";
    Datei.open(tmpSysFile.data(), ios::in);

    getline(Datei, text);
    vendor = text;
    //remoce spaces at the end of the string
    while (vendor.find_last_of(" ") == vendor.size() - 1) {

        vendor.erase(vendor.find_last_of(" "));
    }
    if (!vendor.empty()) {
        cout << "vendor gefunden: " << vendor << endl;
    } else {
        cerr << "kein vendor name gefunden" << endl;
        vendor = "n/a";
    }
    Datei.close();
*/

    fetchTableauParmStats();
}

/**
 * @brief scans drives with tableau-parm
 */
void Device::fetchTableauParmStats() {
    FILE *tableau_parm;
    string command, temp_buf;
    int len, found_pos;
    char buffer[127];


    //TODO nicer authentication...
    //if (getenv("USER") != "root") {
    //    command = "gksu  --message 'gTableauParm need root privileges to execute \"tableau-parm\" propably. Please enter root password below:'  tableau-parm /dev/" + getDeviceName();
    //} else {
        command = "tableau-parm /dev/" + getDeviceName();
    //}
    if ((tableau_parm = popen(command.data(), "r")) == NULL) {
        perror("popen()");

    }
    while (fgets(buffer, sizeof ( buffer), tableau_parm) != NULL) {
        // parse tableau-parm output
        temp_buf = buffer;
        len = temp_buf.length();
        temp_buf.erase(len - 1, len);
        cout << "tableau-parm: " << temp_buf << endl;
        if (temp_buf.find("chan_index:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_chan_index = temp_buf;

        } else if (temp_buf.find("chan_type:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_chan_type = temp_buf;
        } else if (temp_buf.find("writes_permitted:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_writes_permitted = (temp_buf == "TRUE" ? true : false);
            //cout << "writes[" << temp_buf << "]:" << (bridge_writes_permitted ? "yes" : "no") << endl;
        } else if (temp_buf.find("declare_write_blocked:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_declare_write_blocked = (temp_buf == "TRUE" ? true : false);
            //cout << "writes[" << temp_buf << "]:" << (bridge_declare_write_blocked ? "yes" : "no") << endl;
        } else if (temp_buf.find("declare_write_errors:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_declare_write_errors = (temp_buf == "TRUE" ? true : false);
        } else if (temp_buf.find("bridge_serial:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_serial = temp_buf;
        } else if (temp_buf.find("bridge_vendor:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_vendor = temp_buf;
        } else if (temp_buf.find("bridge_model:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_model = temp_buf;
        } else if (temp_buf.find("firmware_date:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_firmware_date = temp_buf;
        } else if (temp_buf.find("firmware_time:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_firmware_time = temp_buf;
        } else if (temp_buf.find("drive_vendor:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_drive_vendor = temp_buf;
        } else if (temp_buf.find("drive_model:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_drive_model = temp_buf;
        } else if (temp_buf.find("drive_serial:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_drive_serial = temp_buf;
        } else if (temp_buf.find("drive_revision:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_drive_revision = temp_buf;
        } else if (temp_buf.find("security_in_use:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_security_in_use = (temp_buf == "TRUE" ? true : false);
        } else if (temp_buf.find("security_support:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_security_support = (temp_buf == "TRUE" ? true : false);
        } else if (temp_buf.find("hpa_in_use:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_hpa_in_use = (temp_buf == "TRUE" ? true : false);
        } else if (temp_buf.find("hpa_support:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_hpa_support = (temp_buf == "TRUE" ? true : false);
            //cout << "hpa support[" << temp_buf << "]:" << (bridge_hpa_support ? "yes" : "no") << endl;
        } else if (temp_buf.find("dco_in_use:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_dco_in_use = (temp_buf == "TRUE" ? true : false);
        } else if (temp_buf.find("dco_support:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_dco_support = (temp_buf == "TRUE" ? true : false);
        } else if (temp_buf.find("drive_capacity:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_drive_capacity = temp_buf.data();
        } else if (temp_buf.find("hpa_capacity:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_hpa_capacity = temp_buf.data();
        } else if (temp_buf.find("dco_capacity:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_dco_capacity = temp_buf.data();
        } else if (temp_buf.find("dco_challenge_key:")) {
            found_pos = temp_buf.find(": ");
            temp_buf.erase(0, found_pos + 2);
            bridge_dco_challenge_key = temp_buf;
        }

        temp_buf.clear();
    }
    pclose(tableau_parm);
}

struct hd_geometry* Device::get_geometry(int fd)
{
    static struct hd_geometry geometry;

    if (ioctl(fd, HDIO_GETGEO, &geometry)) {
        perror("ERROR: HDIO_GETGEO failed");
    }
    return &geometry;
}
 
void* Device::get_diskinfo(int fd)
{
    static __u8 args[4+512];
    __u16 *id = (__u16 *)(args + 4);
    int i;

    memset(args, 0, sizeof(args));
    args[0] = ATA_IDENTIFY;
    args[3] = 1;
    if (ioctl(fd, HDIO_DRIVE_CMD, args)) {
        args[0] = ATA_PIDENTIFY;
        args[1] = 0;
        args[2] = 0;
        args[3] = 1;
        if (ioctl(fd, HDIO_DRIVE_CMD, args)) {
            perror("ERROR: HDIO_DRIVE_CMD failed");
        }
    }
 
    /* byte-swap data to match host endianness */
    for (i = 0; i < 0x100; ++i)
         __le16_to_cpus(&id[i]);
    return id;
}
 
//
// Routine currently only handles SATA drives.  
// Extra code needs to be added to support PATA, SCSI, USB, etc.
//
char* Device::get_transport(__u16 id[])
{
    __u16 major, minor;
    unsigned int ttype, stype;
 
    major = id[TRANSPORT_MAJOR];
    minor = id[TRANSPORT_MINOR];
 
    if (major == 0x0000 || major == 0xffff)
        return (char *)"";
 
    ttype = major >> 12;      /* transport type */
    stype = major & 0xfff;    /* subtype */
 
	cout<<"transport: "<<ttype<<" subtype: " <<stype<<endl;
    if (ttype == 1) {
         if (stype & 0x2f) {
             if (stype & (1<<5))
                 return (char *)"SATA Rev 3.0";
             else if (stype & (1<<4))
                 return (char *)"SATA Rev 2.6";
             else if (stype & (1<<3))
                 return (char *)"SATA Rev 2.5";
             else if (stype & (1<<2))
                 return (char *)"SATA II Extensions";
             else if (stype & (1<<1))
                 return (char *)"SATA 1.0a";
         }
    } else if (ttype == 8) {
		return (char*)"USB";
	}
    return (char *)"n/a";
}
 
char* Device::get_rpm(__u16 id[])
{
   static char str[6];
   __u16 i = id[NMRR];
 
   sprintf(str,"%u", i);
 
   return str;
}
 
char* Device::ascii_string(__u16 *p, unsigned int len)
{
    __u8 i, c;
    char cl;
    static char str[60];
    char *s = str;
 
    memset(&str, 0, sizeof(str));
 
    /* find first character */
    for (i = 0; i < len; i++) {
        if (( (char)0x00ff & ((*p) >> 8)) != ' ')
            break;
        if ((cl = (char) 0x00ff & (*p)) != ' ') {
            if (cl != '\0') *s++ = cl;
            p++; i++;
            break;
        }
        p++;
    }
    /* copy from here to end */
    for (; i < len; i++) {
        c = (*p) >> 8;
        if (c) *s++ = c;
        c = (*p);
        if (c) *s++ = c;
        p++;
    }
 
    /* remove trailing blanks */
    s = str;
    while(*s) s++;
    while(*--s == ' ') *s= 0;
 
    return str;
}
 
#define USE_CAPAB
char* Device::get_capacity(int fd, __u16 id[])
{
    unsigned int sector_bytes = 512;
    static char str[20];
    __u64 sectors = 0;
 
#ifdef USE_CAPAB
    memset(&str, 0, sizeof(str));
 
    if (id[CAPAB] & LBA_SUP) {
        if (((id[CMDS_SUPP_1] & VALID) == VALID_VAL) && (id[CMDS_SUPP_1] & SUPPORT_48_BIT) ) {
            sectors = (__u64)id[LBA_64_MSB] << 48 | (__u64)id[LBA_48_MSB] << 32 |
                      (__u64)id[LBA_MID] << 16 | id[LBA_LSB] ;
        }
    }
#else
    unsigned int sector32 = 0;
 
    if (!(ioctl(fd, BLKGETSIZE64, &sectors))) {            // bytes
          sectors /= sector_bytes;
    } else if (!(ioctl(fd, BLKGETSIZE, &sector32))) {      // sectors
          sectors = sector32;
    } else
          return "";
#endif
 
    sectors *= (sector_bytes /512);
    sectors = (sectors << 9)/1000000;
    if (sectors > 1000)
        sprintf(str, "%lluGB", (unsigned long long) sectors/1000);
    else
        sprintf(str, "%lluMB", (unsigned long long) sectors);
 
    return str;
}
 
void Device::dump_partitions(char *device, int dumpmode, int nlmode)
{
    PedDevice *dev = (PedDevice *)NULL;
    //PedDiskType* type;
    PedDisk* disk = (PedDisk *)NULL;
    PedPartition* part;
    PedPartitionFlag flag;
    PedUnit default_unit;
    int has_free_arg = 0;
 
    char *start;
    char *end;
    char *size;
    char flags[100];
    //const char *partname;
    const char *parttype;
    const char *partlabel;
    const char *partflags;
    int first_flag;
 
    dev = ped_device_get(device);
    if (!ped_device_open (dev)) {
       fprintf(stderr, "ERROR: ped-device-opem\n");
       exit(1);
    }
 
    disk = ped_disk_new(dev);
    if (!disk) {
       fprintf(stderr, "ERROR: ped-disk-new\n");
       exit(1);
    }
 
    start = ped_unit_format(dev, 0);
    default_unit = ped_unit_get_default();
    end = ped_unit_format_byte (dev, dev->length * dev->sector_size
          - (default_unit == PED_UNIT_CHS || default_unit == PED_UNIT_CYLINDER));
 
    //FIXME DUMB
          printf("    Partition Type: %s\n", disk->type->name);
          printf("    No.  Start   End     Size      Type      Filesystem   Name  Flags\n");
                     
 
    free(start);
    free(end);
 
    for (part = ped_disk_next_partition (disk, NULL); part;
        part = ped_disk_next_partition (disk, part)) {
 
         if ((!has_free_arg && !ped_partition_is_active(part)) ||
             part->type & PED_PARTITION_METADATA)
             continue;
 
         start = ped_unit_format (dev, part->geom.start);
         end = ped_unit_format_byte (dev, (part->geom.end + 1) * (dev)->sector_size - 1);
         size = ped_unit_format (dev, part->geom.length);
 
         if (!(part->type & PED_PARTITION_FREESPACE)) {
              parttype = ped_partition_type_get_name (part->type);
              partlabel = ped_partition_get_name(part);
         } else {
              parttype = "";
              partlabel = "";
         }
 
         // flags
         memset(&flags, 0, sizeof(flags));
         first_flag = 1;
         for (flag = ped_partition_flag_next((PedPartitionFlag)0); flag;
              flag = ped_partition_flag_next(flag)) {
              if (ped_partition_get_flag(part, flag)) {
                   if (first_flag) {
                        first_flag = 0;
                   } else {
                        strcat (flags, ", ");
                   }
                   partflags = ped_partition_flag_get_name(flag);
                   strcat(flags, partflags);
              }
         }
         switch (dumpmode) {
             case 9:
                 if (nlmode) printf("\n        ");
                 if (part->num >= 0)
                      printf("<partition number=\"%d\">", part->num);
                 else
                      printf("<partition number=\"0\">");
                 if (nlmode) printf("\n            ");
                 printf("<start>%s</start>", start);
                 if (nlmode) printf("\n            ");
                 printf("<end>%s</end>", end);
                 if (nlmode) printf("\n            ");
                 printf("<size>%s</size>", size);
                 if (nlmode) printf("\n            ");
                 printf("<type>%s</type>", parttype);
                 if (nlmode) printf("\n            ");
                 printf("<filesystem>%s</filesystem>", part->fs_type ? part->fs_type->name : "");
                 if (nlmode) printf("\n            ");
                 printf("<label>%s</label>", partlabel);
                 if (nlmode) printf("\n            ");
                 printf("<flags>%s</flags>", flags);
                 if (nlmode) printf("\n        ");
                 printf("</partition>");
                 break;
             case DUMPTXT:
                 if (part->num >= 0)
                      printf("    %02d", part->num);
                 else
                      printf("        ");
                 printf("  %6s  %6s  %6s  %10s", start, end, size, parttype);
                 printf("  %6s", part->fs_type ? part->fs_type->name : "");
                 printf("  %10s  %s\n", partlabel, flags);
                 break;
             case 8:
                 putchar('"'); putchar(','); putchar('"');
                 if (part->num >= 0) printf("%02d", part->num);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", start);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", end);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", size);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", parttype);
                 putchar('"'); putchar(','); putchar('"');
                 if (part->fs_type) printf("%s", part->fs_type->name);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", partlabel);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", flags);
                 break;
        }
 
        free(start);
        free(end);
        free(size);
    }
}
 
void Device::dump(char *device) {
    int len = strlen(device) + 8;
    int i = 0;
 
    printf("\nDEVICE: %s\n", device);
    while(i++ < len) putchar('-');
    putchar('\n');
    printf("Manufacturer Model: %s\n", ascii_string(&id[27],20));
    model=ascii_string(&id[27],20);
    printf("     Serial Number: %s\n", ascii_string(&id[10],10));
    serial=ascii_string(&id[10],10);
    printf(" Firmware Revision: %s\n", ascii_string(&id[23],4));
    printf("    Transport Type: %s\n", get_transport(id));
    printf("       Maximum RPM: %s\n", get_rpm(id));
    capacity=get_capacity(fd, id);
    printf("          Capacity: %s\n", capacity.data());
    printf("  Number Cylinders: %u\n", g->cylinders);
 
    dump_partitions(device, DUMPTXT, NONEWLINE);
}
